﻿using System;
using System.Linq;

namespace ch05.binary.wasm_reader
{
    public partial struct WasmReader
    {
        private (Instruction[], byte) readInstructions()
        {
            var instrs = Array.Empty<Instruction>();
            byte end = 0x00;
            for (;;)
            {
                var instr = readInstruction();
                if (instr.Opcode == OpCodes.Else || instr.Opcode == OpCodes.End)
                {
                    end = instr.Opcode;
                    return (instrs, end);
                }

                instrs = instrs.Append(instr).ToArray();
            }
        }

        private Instruction readInstruction()
        {
            Instruction instr = new Instruction {Opcode = ReadByte()};
            if (OpNames.GetOpNameArr()[instr.Opcode] == "")
            {
                Panic.Run("undefined opcode: 0x{0:x}", instr.Opcode);
            }

            instr.Args = readArgs(instr.Opcode);
            return instr;
        }

        private object readArgs(byte opcode)
        {
            switch (opcode)
            {
                case OpCodes.Block:
                case OpCodes.Loop:
                    return ReadBlockArgs();
                case OpCodes.If:
                    return ReadIfArgs();
                case OpCodes.Br:
                case OpCodes.BrIf:
                    return ReadVarU32(); // label_idx
                case OpCodes.BrTable:
                    return ReadBrTableArgs();
                case OpCodes.Call:
                    return ReadVarU32(); // func_idx
                case OpCodes.CallIndirect:
                    return ReadCallIndirectArgs();
                case OpCodes.LocalGet:
                case OpCodes.LocalSet:
                case OpCodes.LocalTee:
                    return ReadVarU32(); // local_idx
                case OpCodes.GlobalGet:
                case OpCodes.GlobalSet:
                    return ReadVarU32(); // global_idx
                case OpCodes.MemorySize:
                case OpCodes.MemoryGrow:
                    return ReadZero();
                case OpCodes.I32Const:
                    return ReadVarS32();
                case OpCodes.I64Const:
                    return ReadVarS64();
                case OpCodes.F32Const:
                    return ReadF32();
                case OpCodes.F64Const:
                    return ReadF64();
                case OpCodes.TruncSat:
                    return ReadByte();
                default:
                    if (opcode >= OpCodes.I32Load && opcode <= OpCodes.I64Store32)
                    {
                        return ReadMemArg();
                    }

                    return null;
            }
        }
    }
}